/*!
 * x-scrollbar 自定义滚动条插件
 * 版本: v2.4.1
 * 作者: 清晨的阳光(QQ:765550360)
 * 许可: MIT
 * https://gitee.com/null_720_0252/x-scrollbar
 */

if (typeof exports === 'object' && typeof module !== 'undefined') {
  module.exports = XScrollbar;
} else {
  window.XScrollbar = XScrollbar;
}

/**
 * @param {*} dom DOM元素
 * @param {*} options 选项
 */
function XScrollbar(dom, options) {
  this.$container = dom;
  if (this.$container.classList.contains('x-scrollbar-container')) return;

  // 移动端检测
  this.isMobile = window.navigator.userAgent.toLowerCase().indexOf('mobile') != -1;
  if (this.isMobile) return;

  // 合并配置
  var defaultOptions = {
    // 禁用Y轴滚动(拨动鼠标滚轮时将作用于X轴)
    disabledScrollY: false,
    // 响应容器和内容大小改变(自动更新滚动条)
    autoUpdate: true,
    // 自动隐藏
    autoHide: true,
    // 阻止向上传递滚动事件
    preventDefault: true,
    // 平滑滚动
    smooth: true
  };
  this.assign(defaultOptions, options);
  this.assign(this, defaultOptions);

  // 构造dom
  this.$content = this.html2dom('<div class="x-scrollbar__content"></div>');
  this.$railX = this.html2dom('<div class="x-scrollbar__rail-x"></div>');
  this.$railY = this.html2dom('<div class="x-scrollbar__rail-y"></div>');
  this.$thumbX = this.html2dom('<div class="x-scrollbar__thumb-x"></div>');
  this.$thumbY = this.html2dom('<div class="x-scrollbar__thumb-y"></div>');
  this.$railX.appendChild(this.$thumbX);
  this.$railY.appendChild(this.$thumbY);
  var childNodes = [];
  Array.prototype.forEach.call(this.$container.childNodes, function (node) { childNodes.push(node) });
  childNodes.forEach((function (node) { this.$content.appendChild(node); }).bind(this));
  this.$container.appendChild(this.$content);
  this.$container.appendChild(this.$railX);
  this.$container.appendChild(this.$railY);

  // 自动隐藏
  this.$container.classList.add('x-scrollbar-container');
  if (!this.autoHide) this.$container.classList.add('x-scrollbar-keep');

  // 绑定事件
  this.bindDrag();
  this.bindWheel();
  this.bindScroll();

  // 自动更新
  if (this.autoUpdate) {
    // 首次自动触发
    this.resizeObserver();
  } else {
    this.update();
  }
}

/**
 * 设置滑块大小
 */
XScrollbar.prototype.setThumbSize = function () {
  // (clientWidth / scrollWidth) = (滑块大小 / clientWidth)
  // 最大滑动距离 = clientWidth - 滑块大小
  // 最大滚动距离 = scrollWidth - clientWidth
  // (滑动距离 / 最大滑动距离) = (滚动距离 / 最大滚动距离)

  // 容器大小
  this.clientWidth = this.$container.clientWidth;
  this.clientHeight = this.$container.clientHeight;
  // 内容大小
  this.scrollWidth = this.$container.scrollWidth;
  this.scrollHeight = this.$container.scrollHeight;
  //是否存在滚动条
  this.hasXScrollbar = this.scrollWidth > this.clientWidth;
  this.hasYScrollbar = this.scrollHeight > this.clientHeight;
  //滑块大小
  this.thumbXWidth = Math.max((this.clientWidth / this.scrollWidth) * this.clientWidth, 30);
  this.thumbYHeight = Math.max((this.clientHeight / this.scrollHeight) * this.clientHeight, 30);
  //最大滑动距离
  this.thumbXMaxLeft = this.clientWidth - this.thumbXWidth;
  this.thumbYMaxTop = this.clientHeight - this.thumbYHeight;
  //最大滚动距离
  this.maxScrollLeft = this.scrollWidth - this.clientWidth;
  this.maxScrollTop = this.scrollHeight - this.clientHeight;

  this.$railX.style.display = this.hasXScrollbar ? 'block' : 'none';
  this.$railY.style.display = this.hasYScrollbar ? 'block' : 'none';
  this.$thumbX.style.width = this.thumbXWidth + 'px';
  this.$thumbY.style.height = this.thumbYHeight + 'px';
};

/**
 * 拖动
 */
XScrollbar.prototype.bindDrag = function () {
  // 是否被拖动选定
  var thumbXActive = false;
  var thumbYActive = false;
  // 上一次的拖动位置
  var screenX = null;
  var screenY = null;

  this.$thumbX.addEventListener('mousedown', (function (e) {
    this.$railX.classList.add('x-scrollbar__rail-draging');
    thumbXActive = true;
    screenX = e.screenX;
  }).bind(this));

  this.$thumbY.addEventListener('mousedown', (function (e) {
    this.$railY.classList.add('x-scrollbar__rail-draging');
    thumbYActive = true;
    screenY = e.screenY;
  }).bind(this));

  this.scrollDocumentMouseup = (function (e) {
    this.$railX.classList.remove('x-scrollbar__rail-draging');
    this.$railY.classList.remove('x-scrollbar__rail-draging');
    thumbXActive = false;
    thumbYActive = false;
  }).bind(this);
  document.addEventListener('mouseup', this.scrollDocumentMouseup);

  this.scrollDocumentMousemove = (function (e) {
    if (!(thumbXActive || thumbYActive)) return;
    e.preventDefault();

    var axis = null;
    var positionValue = null;
    var scrollValue = null;

    if (thumbXActive) {
      var offset = e.screenX - screenX;
      screenX = e.screenX;
      axis = 'x';
      positionValue = Math.max(Math.min((this.left || 0) + offset, this.thumbXMaxLeft), 0);
      scrollValue = Number(positionValue / this.thumbXMaxLeft * this.maxScrollLeft);
    } else {
      var offset = e.screenY - screenY;
      screenY = e.screenY;
      axis = 'y';
      positionValue = Math.max(Math.min((this.top || 0) + offset, this.thumbYMaxTop), 0);
      scrollValue = Number(positionValue / this.thumbYMaxTop * this.maxScrollTop);
    }

    this.scroll(axis, positionValue, scrollValue);
  }).bind(this);
  document.addEventListener('mousemove', this.scrollDocumentMousemove);
};

/**
 * 滚动
 */
XScrollbar.prototype.bindWheel = function () {
  var onWheel = (function (e, axis, delta) {
    // 将滚动量转为像数值(https://developer.mozilla.org/zh-CN/docs/Web/API/Element/wheel_event)
    if (e.deltaMode != 0) {
      // 一行默认为40px
      delta = delta * 40;
    }

    delta = parseInt(delta);

    var positionValue = null;
    var scrollValue = null;

    if (axis == 'x') {
      scrollValue = Math.max(Math.min((this.scrollLeft || 0) + delta, this.maxScrollLeft), 0);
      positionValue = Number(scrollValue / this.maxScrollLeft * this.thumbXMaxLeft);
    } else {
      scrollValue = Math.max(Math.min((this.scrollTop || 0) + delta, this.maxScrollTop), 0);
      positionValue = Number(scrollValue / this.maxScrollTop * this.thumbYMaxTop);
    }

    // 阻止向上传递 || !(终点)
    var over = scrollValue == (axis == 'x' ? this.scrollLeft : this.scrollTop);
    if (this.preventDefault || !over) {
      e.preventDefault();
      e.stopPropagation();
    }
    // 终点
    if(over) return;

    if (this.mouseType == null) {
      if (Math.abs(delta) < 50) {
        // 初始滚动量小于90定义为触控板
        this.mouseType = 'touchpad';
      } else {
        this.mouseType = 'wheel';
      }
    }

    this.scroll(axis, positionValue, scrollValue, (this.smooth && this.mouseType == 'wheel'));
  }).bind(this);


  this.onWheel = (function (e) {
    // e.deltaX 正值向下, 负值向上
    if (e.deltaX != 0 && this.hasXScrollbar) {
      onWheel(e, 'x', e.deltaX);
    }
    if (e.deltaY != 0) {
      // (shift键被按下 或 禁用Y轴滚动), 作用于x轴
      if ((e.deltaX == 0) && (e.shiftKey || this.disabledScrollY) && this.hasXScrollbar) {
        onWheel(e, 'x', e.deltaY);
      } else if (this.hasYScrollbar) {
        onWheel(e, 'y', e.deltaY);
      }
    }
  }).bind(this);
  this.$container.addEventListener('wheel', this.onWheel);
};

/**
 * 监听滚动事件
 * 用于响应从外部设置 scrollLeft/scrollTop 修正滚动条
 */
XScrollbar.prototype.bindScroll = function () {
  this.onScroll = (function (e) {
    if (this.innerScroll) return;
    this.update();
  }).bind(this);
  this.$container.addEventListener('scroll', this.onScroll);
};

/**
 * 滚动、修正滑块
 * @param {*} axis 
 * @param {*} positionValue 
 * @param {*} scrollValue 
 * @param {*} smooth = false
 */
XScrollbar.prototype.scroll = function (axis, positionValue, scrollValue, smooth) {
  // 起始值
  var left = this.left;
  var scrollLeft = this.scrollLeft;
  var top = this.top;
  var scrollTop = this.scrollTop;

  // 结束值
  if (axis == 'x') {
    this.left = positionValue;
    this.scrollLeft = scrollValue;
  } else {
    this.top = positionValue;
    this.scrollTop = scrollValue;
  }

  if (axis == 'x') {
    if (this.xRefId) return;
  } else {
    if (this.yRefId) return;
  }

  var end = (function () {
    if (!(this.xRefId || this.yRefId)) {
      requestAnimationFrame((function () { this.innerScroll = false }).bind(this));
    }
  }).bind(this);

  var animate = (function () {
    // 从组件内部触发的滚动事件
    this.innerScroll = true;

    if (axis == 'x') {
      left = this.easeout(left, this.left, smooth);
      scrollLeft = this.easeout(scrollLeft, this.scrollLeft, smooth);

      // 滑块
      this.$thumbX.style.left = left + 'px';
      // 滚动
      this.$container.scrollLeft = scrollLeft;
      // 轨道
      this.$railX.style.left = scrollLeft + 'px';
      this.$railY.style.right = (-scrollLeft) + 'px';

      if (Math.abs(scrollLeft - this.scrollLeft) >= 1) {
        this.xRefId = requestAnimationFrame(animate);
      } else {
        this.xRefId = null;
        end();
      }
    } else {
      top = this.easeout(top, this.top, smooth);
      scrollTop = this.easeout(scrollTop, this.scrollTop, smooth);

      this.$thumbY.style.top = top + 'px';
      this.$container.scrollTop = scrollTop;
      this.$railX.style.bottom = (-scrollTop) + 'px';
      this.$railY.style.top = scrollTop + 'px';

      if (Math.abs(scrollTop - this.scrollTop) >= 1) {
        this.yRefId = requestAnimationFrame(animate);
      } else {
        this.yRefId = null;
        end();
      }
    }
  }).bind(this);
  requestAnimationFrame(animate);
};

/**
 * 缓动效果
 * @param {*} start 
 * @param {*} end 
 * @param {*} smooth 
 * @returns 
 */
XScrollbar.prototype.easeout = function (start, end, smooth) {
  var v = null;

  if (!smooth || (Math.abs(end - start) <= 1)) {
    v = end;
  } else {
    v = start + (end - start) / 4;
  }

  if ((end - start) > 0) {
    return Math.ceil(v);
  } else {
    return Math.floor(v);
  }
};

/**
 * 使用滚动值修正滑块
 * 在 容器大小 或 内容大小 发生改变时调用
 */
XScrollbar.prototype.update = function () {
  if (this.isMobile) return;

  // 先重置以更新滚动条的位置
  this.$railX.style.display = 'none';
  this.$railY.style.display = 'none';
  var scrollLeft = this.$container.scrollLeft;
  var scrollTop = this.$container.scrollTop;

  this.setThumbSize();

  var positionLeft = Number(scrollLeft / this.maxScrollLeft * this.thumbXMaxLeft) || 0;
  this.scroll('x', positionLeft, scrollLeft);

  var positionTop = Number(scrollTop / this.maxScrollTop * this.thumbYMaxTop) || 0;
  this.scroll('y', positionTop, scrollTop);
};

/**
 * 响应容器和内容大小改变, 自动更新滚动条
 */
XScrollbar.prototype.resizeObserver = function () {
  if (typeof ResizeObserver != 'undefined') {
    this.$resizeObserver = new ResizeObserver((function (entries) {
      var contentRect = entries[0].contentRect;
      if (!(contentRect.width || contentRect.height)) return;
      this.update();
    }).bind(this));
    this.$resizeObserver.observe(this.$container);
    this.$resizeObserver.observe(this.$content);
    return;
  }

  var containerRect = this.$container.getBoundingClientRect();
  var contentRect = this.$content.getBoundingClientRect();
  var resizeObserver = (function () {
    var _containerRect = this.$container.getBoundingClientRect();
    var _contentRect = this.$content.getBoundingClientRect();
    if ((containerRect.width != _containerRect.width) || (containerRect.height != _containerRect.height) || (contentRect.width != _contentRect.width) || (contentRect.height != _contentRect.height)) {
      this.update();
    }
    containerRect = _containerRect;
    contentRect = _contentRect;
    this.resizeObserverReqId = requestAnimationFrame(resizeObserver);
  }).bind(this);
  requestAnimationFrame(resizeObserver);
  this.update();
};

/**
 * 销毁
 */
XScrollbar.prototype.destroy = function () {
  if (!this.$container.classList.contains('x-scrollbar-container')) return;
  if (this.isMobile) return;
  if (this.$resizeObserver) {
    this.$resizeObserver.disconnect();
  }
  if (this.resizeObserverReqId) {
    cancelAnimationFrame(this.resizeObserverReqId);
  }
  this.$container.classList.remove('x-scrollbar-container');
  this.$container.classList.remove('x-scrollbar-keep');
  var childNodes = [];
  Array.prototype.forEach.call(this.$content.childNodes, function (node) { childNodes.push(node) });
  childNodes.forEach((function (node) { this.$container.appendChild(node) }).bind(this));
  this.$container.removeChild(this.$content);
  this.$container.removeChild(this.$railX);
  this.$container.removeChild(this.$railY);
  document.removeEventListener('mouseup', this.scrollDocumentMouseup);
  document.removeEventListener('mousemove', this.scrollDocumentMousemove);
  this.$container.removeEventListener('wheel', this.onWheel);
  this.$container.removeEventListener('scroll', this.onScroll);
};

/**
 * html 转 dom
 * @param {*} html 
 * @returns 
 */
XScrollbar.prototype.html2dom = function (html) {
  var element = document.createElement('div');
  element.innerHTML = html;
  var children = element.children;
  if (children.length <= 1) {
    return children[0];
  } else {
    return children;
  }
};

/**
 * 合并配置
 * @param {*} target 
 * @param {*} source 
 * @returns 
 */
XScrollbar.prototype.assign = function (target, source) {
  target = target || {};
  source = source || {};
  for (var key in source) {
    target[key] = source[key];
  }
  return target;
};